package org.jsea.meta.api.service;

import org.jsea.meta.api.bean.*;
import org.jsea.meta.api.constant.ActionType;
import org.jsea.meta.api.constant.OrderType;
import org.jsea.meta.api.constant.WhereCond;
import org.jsea.meta.api.service.fmt.*;

import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.sql.Timestamp;
import java.time.temporal.TemporalAccessor;
import java.util.*;
import java.util.function.BiConsumer;

/**
 * --.
 * *
 * author   Jiang.hy
 * create   2022-04-09
 */
public class MetaApiProcessor {

    private final MetaApiAccessHandler handler;

    public MetaApiProcessor(MetaApiAccessHandler handler) {
        this.handler = handler;
    }

    /**
     * 构建格式化工具
     *
     * @param type    格式化类型
     * @param pattern pattern
     * @return 工具
     */
    public static MetaApiFormat format(String type, String pattern) {
        return switch (type) {
            case "T" -> MetaApiTimeFormat.gen(pattern);
            case null -> MetaApiTimeFormat.gen(pattern);
            case "#" -> new MetaApiValueFormat();
            case "A" -> new MetaApiArrayFormat(pattern);
            case "N" -> new MetaApiDecimalFormat(pattern);
            default ->
                    throw new IllegalArgumentException("format type error : [" + type + "] T->time N->number #->json A->array");
        };
    }

    /**
     * split by ' '
     * 最多切割两段，余下的丢弃
     *
     * @param src str
     * @param c   bc
     */
    public static void split(String src, BiConsumer<String, String> c) {
        char regex = ' ';
        int offset = src.indexOf(regex);
        if (offset < 0) {
            c.accept(check(src), null);
        } else {
            String ss = src.substring(offset + 1);
            int idx = ss.indexOf(regex);
            if (idx < 0) {
                c.accept(check(src.substring(0, offset)), check(ss));
            } else {
                c.accept(check(src.substring(0, offset)), check(ss.substring(0, idx)));
            }
        }
    }

    /**
     * 验证参数是否包含可能引起sql注入问题的符号
     */
    private static final String ILLEGAL_CHARS = "'\"`(/\\;";

    private static String check(String src) {
        for (int i = 0; i < src.length(); i++) {
            if (ILLEGAL_CHARS.indexOf(src.charAt(i)) != -1) {
                throw new IllegalArgumentException("Illegal Argument [" + src + "]");
            }
        }
        return src;
    }

    /**
     * 多次切割，值放入list中
     *
     * @param src       src str
     * @param delimiter 分隔符
     * @param node      node
     */
    private static void split(String src, String delimiter, MetaApiNode<String> node) {
        int offset = src.indexOf(delimiter);
        if (offset < 0) {
            node.setNext(new MetaApiNode<>(src));
            return;
        }
        MetaApiNode<String> cur = new MetaApiNode<>(src.substring(0, offset));
        node.setNext(cur);
        split(src.substring(offset + delimiter.length()), delimiter, cur);
    }

    public MetaApiDefinitionBean generate(MetaApiSaveRequest request) {
        MetaApiDefinitionBean bean = getBean(ActionType.insert, request.getFrom());
        fmt(request.getFmt(), bean);
        ApiTableInfo table = table(request.getFrom(), bean.getAction());
        bean.setTi(table);
        if (request.isAsync() || request.getBack() == null) {
            // 异步也要设置back，避免报错
            bean.setBack(new String[]{table.getPrimaryKey()});
        } else {
            if (request.getBack() instanceof Collection<?> back) {
                Set<String> keys = new HashSet<>(back.size());
                for (Object key : back) {
                    keys.add(key.toString());
                }
                bean.setBack(keys.toArray(new String[0]));
            } else {
                bean.setBack(new String[]{request.getBack().toString()});
            }
        }
        return bean;
    }

    public MetaApiDefinitionBean generate(MetaApiGetRequest request) {
        if (request.getId() == null)
            throw new IllegalArgumentException("id is null");

        MetaApiDefinitionBean bean = getBean(ActionType.select, request.getFrom());
        fmt(request.getFmt(), bean);
        if (request.getId() instanceof Collection) {
            bean.setId(((Collection<?>) request.getId()).iterator().next());
        } else {
            bean.setId(request.getId());
        }
        bean.setTi(table(request.getFrom(), bean.getAction()));
        column(request.getSelect(), bean);
        List<MetaApiCond> where = where(request.getWhere(), bean.getFormat());
        if (where != null) {
            bean.setConditions(where);
        }
        return bean;
    }

    public MetaApiDefinitionBean generate(MetaApiFindRequest request) {
        MetaApiDefinitionBean bean = getBean(ActionType.select, request.getFrom());
        fmt(request.getFmt(), bean);
        bean.setTi(table(request.getFrom(), bean.getAction()));
        column(request.getSelect(), bean);
        List<MetaApiCond> where = where(request.getWhere(), bean.getFormat());
        if (where != null) {
            bean.getConditions().addAll(where);
        }
        order(request.getOrder(), bean);
        bean.setPage(request.getPage());
        bean.setSize(request.getSize());
        return bean;
    }

    public MetaApiDefinitionBean generate(MetaApiDelRequest request) {
        if (request.getId() == null) {
            throw new IllegalArgumentException("id is null");
        } else if (request.getId() instanceof Collection) {
            if (((Collection<?>) request.getId()).isEmpty()) {
                throw new IllegalArgumentException("id is empty");
            }
        }

        MetaApiDefinitionBean bean = getBean(ActionType.delete, request.getFrom());
        bean.setId(request.getId());
        fmt(request.getFmt(), bean);
        bean.setTi(table(request.getFrom(), bean.getAction()));
        List<MetaApiCond> where = where(request.getWhere(), bean.getFormat());
        if (where != null) {
            bean.setConditions(where);
        }
        return bean;
    }

    public MetaApiDefinitionBean generate(MetaApiUpdateRequest request) {
        if (request.getId() == null)
            throw new IllegalArgumentException("id is null");
        if (request.getData() == null)
            throw new IllegalArgumentException("data is null");
        MetaApiDefinitionBean bean = getBean(ActionType.update, request.getFrom());
        fmt(request.getFmt(), bean);
        bean.setId(request.getId());
        bean.setTi(table(request.getFrom(), bean.getAction()));
        bean.setData(request.getData());
        List<MetaApiCond> where = where(request.getWhere(), bean.getFormat());
        if (where != null) {
            bean.setConditions(where);
        }
        return bean;
    }

    /**
     * get join gen
     *
     * @param data   all data
     * @param from   from
     * @param join   join data
     * @param format fmt
     * @return bean
     */
    public MetaApiDefinitionBean generateGetjoin(Map<String, Object> data, String from, MetaApiJoinRequest join,
                                                 Map<String, MetaApiFormat> format) {
        if (when(join.getWhen(), data)) {
            return null;
        }
        MetaApiDefinitionBean bean = getBean(ActionType.select, from);
        table(from, bean);

        List<MetaApiCond> where = where(join.getWhere(), format);
        if (where != null) {
            bean.getConditions().addAll(bean.getConditions());
        }
        column(join.getSelect(), bean);
        //关联关系
        List<KV<String, String>> rels = new ArrayList<>();

        Map<String, String> rel = join.getOn();
        if (rel == null || rel.isEmpty()) {
            throw new IllegalArgumentException("missing association : " + from);
        }
        for (Map.Entry<String, String> entry : rel.entrySet()) {
            String column = entry.getKey();
            String vs = entry.getValue();
            Object value = findValue(data, vs);
            if (value == null) {
                continue;
            }
            bean.getConditions().add(new MetaApiCond(column, WhereCond.eq, value));
            createRels(bean, rels, vs, column);
        }
        if (rels.isEmpty()) {
            bean.setSize(0);
            return bean;
        }
        bean.setRelations(rels);
        bean.setPage(join.getPage());
        bean.setSize(join.getSize());
        order(join.getOrder(), bean);
        return bean;
    }

    /**
     * get 接口根据条件判断是否处理关联数据
     * when 为空时，默认处理join信息
     *
     * @param when 关联限制
     * @param data 主表数据
     * @return true 跳过该条join
     */
    private boolean when(Map<String, ?> when, Map<String, Object> data) {
        if (when == null || when.isEmpty()) {
            return false;
        }
        Set<String> keys = when.keySet();
        for (String key : keys) {
            Object o = when.get(key);
            if (o == null) {
                continue;
            }
            //结果集中找到的值是空的，则跳过join处理
            Object value = findValue(data, key);
            if (value == null) {
                return true;
            }
            if (!compare(o, value)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 数据比较，如果是集合，则比较内部元素
     * 将数据转化为String比较，避免equals方法中因为类型不同而返回false
     *
     * @param a 可以是集合
     * @param b b
     * @return compare result
     */
    public final boolean compare(Object a, Object b) {
        if (a == null || b == null) {
            return false;
        }
        if (a instanceof Collection) {
            String bs = b.toString();
            for (Object val : (Collection<?>) a) {
                if (bs.equals(val.toString())) {
                    return true;
                }
            }
            return false;
        }
        //使用String.equals比较，避免Long和Integer比较出现不等情况
        return a.toString().equals(b.toString());
    }

    /**
     * 建立关联关系
     * 并补充缺少的查询列
     * aid@ : bid
     *
     * @param bean 定义
     * @param rel  关系列表
     * @param vs   被关联字段 bid
     * @param col  关联字段 aid@
     */
    private void createRels(MetaApiDefinitionBean bean, List<KV<String, String>> rel, String vs, String col) {
        rel.add(new KV<>(vs, col));
        if (bean.getAc() != null && !bean.getAc().contains(col)) {
            bean.getColumn().add(new MetaApiField(col));
        }
    }

    /**
     * 列表关联转换
     *
     * @param maps 主列表数据
     * @param from 关联表
     * @param join 关联信息
     * @return bean
     */
    public MetaApiDefinitionBean generateFindjoin(List<Map<String, Object>> maps, String from, MetaApiJoinRequest join,
                                                  Map<String, MetaApiFormat> format) {
        MetaApiDefinitionBean bean = getBean(ActionType.select, from);
        List<MetaApiCond> where = where(join.getWhere(), format);
        if (where != null) {
            bean.getConditions().addAll(where);
        }
        column(join.getSelect(), bean);
        //关联关系
        List<KV<String, String>> rels = new ArrayList<>();
        boolean isNull = false;
        Map<String, String> rel = join.getOn();
        if (rel == null || rel.isEmpty()) {
            throw new IllegalArgumentException("missing association : " + from);
        }
        for (Map.Entry<String, String> entry : rel.entrySet()) {
            String column = entry.getKey();
            String vs = entry.getValue();
            Set<Object> values = new HashSet<>();

            for (Map<String, Object> data : maps) {
                Object value = findValue(data, vs);
                if (value == null) {
                    continue;
                }
                if (value instanceof Collection) {
                    values.addAll((Collection<?>) value);
                } else {
                    values.add(value);
                }
            }

            if (values.isEmpty()) {
                isNull = true;
                break;
            }
            bean.getConditions().add(new MetaApiCond(column, WhereCond.in, values));
            createRels(bean, rels, vs, column);
        }
        table(from, bean);
        if (isNull) {
            bean.setSize(0);
            return bean;
        }
        bean.setRelations(rels);
        bean.setPage(join.getPage());
        bean.setSize(join.getSize());
        order(join.getOrder(), bean);
        return bean;
    }

    @SuppressWarnings("unchecked")
    private void column(Object cos, MetaApiDefinitionBean bean) {
        if (cos instanceof Collection) {
            columnByCollection((Collection<String>) cos, bean);
        } else if (cos instanceof String) {
            columnByCollection(Collections.singleton((String) cos), bean);
        }
    }

    private void columnByCollection(Collection<String> ls, MetaApiDefinitionBean bean) {
        if (ls == null || ls.isEmpty()) return;

        List<MetaApiField> column = new ArrayList<>(ls.size());
        List<String> ats = new ArrayList<>(ls.size());
        for (String s : ls) {
            if (s == null || s.isEmpty()) continue;
            split(s, (a, b) -> {
                column.add(new MetaApiField(a, b));
                ats.add((b == null ? a : b));
            });
        }
        bean.setSingle(column.size() == 1);
        bean.setColumn(column);
        bean.setAc(ats);
    }

    public final List<MetaApiField> column(Collection<String> ls) {
        if (ls == null || ls.isEmpty()) return null;

        List<MetaApiField> column = new ArrayList<>(ls.size());
        ls.forEach(s -> column.add(new MetaApiField(s)));
        return column;
    }

    @SuppressWarnings("unchecked")
    private List<MetaApiCond> where(Object where, Map<String, MetaApiFormat> format) {
        if (where == null) return null;

        List<MetaApiCond> conditions = new ArrayList<>();
        if (where instanceof Map) {
            processwhere(format, (Map<String, ?>) where, conditions);
        } else if (where instanceof List) {
            for (Object item : (List<?>) where) {
                processwhere(format, (Map<String, ?>) item, conditions);
            }
        }

        return conditions;
    }

    @SuppressWarnings("unchecked")
    private void processwhere(Map<String, MetaApiFormat> format, Map<String, ?> itemMap, List<MetaApiCond> conditions) {
        List<Map<String, ?>> orItems = (List<Map<String, ?>>) itemMap.remove("|");
        if (orItems != null && !orItems.isEmpty()) {
            List<MetaApiCond> orGroupConditions = new ArrayList<>();
            for (Map<String, ?> orItem : orItems) {
                if (orItem.isEmpty()) {
                    continue;
                }
                List<MetaApiCond> andConditions = new ArrayList<>();
                orItem.forEach((key, v) -> split(key, (a, b) -> {
                    WhereCond cond = WhereCond.get(b);
                    if (v == null || v == "") {
                        if (cond != WhereCond.is && cond != WhereCond.is_not)
                            return;
                    }
                    andConditions.add(new MetaApiCond(a, WhereCond.get(b), fmtValue(v, format.get(a))));
                }));

                if (andConditions.size() == 1) {
                    orGroupConditions.add(andConditions.getFirst());
                } else {
                    orGroupConditions.add(new MetaApiCond(new MetaApiConditionGroup(andConditions, false)));
                }
            }

            if (!orGroupConditions.isEmpty()) {
                conditions.add(new MetaApiCond(new MetaApiConditionGroup(orGroupConditions, true)));
            }
        }
        itemMap.forEach((key, v) -> split(key, (a, b) -> {
            WhereCond cond = WhereCond.get(b);
            if (v == null || v == "") {
                if (cond != WhereCond.is && cond != WhereCond.is_not)
                    return;
            }
            conditions.add(new MetaApiCond(a, cond, fmtValue(v, format.get(a))));
        }));
    }

    /**
     * 主表不支持别名
     */
    private ApiTableInfo table(String table, ActionType at) {
        if (table == null)
            throw new IllegalArgumentException("from is null");
        KV<ApiTableInfo, ?> ar = new KV<>();
        split(table, (a, b) -> ar.key(handler.getTable(a, at)));
        return ar.key();
    }

    /**
     * 关联表信息
     */
    public void table(String table, MetaApiDefinitionBean bean) {
        if (table == null)
            throw new IllegalArgumentException("from is null");
        if (table.charAt(0) == '#') {
            table = table.substring(1);
            bean.setObj(true);
        }
        split(table, (a, b) -> {
            ApiTableInfo ti = handler.getTable(a, bean.getAction());
            bean.setTi(ti);
            if (b == null) {
                bean.setAlias(a);
            } else {
                bean.setAlias(b);
            }
        });
    }

    public void fmt(List<FormatRule> fmt, MetaApiDefinitionBean bean) {
        if (fmt == null || fmt.isEmpty()) {
            bean.setFormat(Collections.emptyMap());
            return;
        }
        Map<String, MetaApiFormat> fmt_map = new HashMap<>();
        bean.setFormat(fmt_map);

        for (FormatRule rule : fmt) {
            Object v = rule.getColumn();
            if (v == null) continue;
            MetaApiFormat format = format(rule.getType(), rule.getTemplate());
            if (v instanceof String) {
                fmt_map.put((String) v, format);
            } else if (v instanceof Collection) {
                ((Collection<?>) v).forEach(o -> fmt_map.put((String) o, format));
            }
        }
    }

    /**
     * 关联关系寻找值
     *
     * @param data 数据
     * @param path 路径
     * @return value
     */
    public Object findValue(Object data, String path) {
        if (data == null || path == null) {
            return null;
        }
        if (path.indexOf('.') > -1) {
            Set<Object> vals = new HashSet<>();
            MetaApiNode<String> root = new MetaApiNode<>(".");
            split(path, ".", root);
            findValueDeep(root.getNext(), data, vals);
            if (vals.isEmpty()) {
                return null;
            }
            return vals;
        }
        return getValue(data, path);
    }

    public void findValueDeep(MetaApiNode<String> node, Object data, Set<Object> vals) {
        Object value = getValue(data, node.getValue());
        if (value == null) return;
        if (value instanceof Collection<?> collection) {
            for (Object o : collection) {
                findValueDeep(node.getNext(), o, vals);
            }
            //尾部才是需要的参数
        } else if (node.getNext() == null) {
            vals.add(value);
        } else {
            findValueDeep(node.getNext(), value, vals);
        }
    }

    public Object getValue(Object data, String key) {
        if (data instanceof Map) {
            return ((Map<?, ?>) data).get(key);
        }
        if (data instanceof List<?> list) {
            if (list.isEmpty()) {
                return null;
            }
            Set<Object> vs = new HashSet<>(list.size());
            for (Object obj : list) {
                vs.add(((Map<?, ?>) obj).get(key));
            }
            return vs;
        }
        return null;
    }

    public void order(Object obj, MetaApiDefinitionBean bean) {
        if (obj == null) return;
        List<?> orders = obj instanceof Collection ? (List<?>) obj : Collections.singletonList(obj);
        Map<String, OrderType> order = new LinkedHashMap<>();
        for (Object o : orders) {
            String s = (String) o;
            if (s.isEmpty()) {
                continue;
            }
            char c = s.charAt(s.length() - 1);
            if (c == '-') {
                order.put(s.substring(0, s.length() - 1), OrderType.desc);
            } else if (c == '+') {
                order.put(s.substring(0, s.length() - 1), OrderType.asc);
            } else {
                order.put(s, OrderType.asc);
            }
        }
        bean.setOrder(order);
    }

    /**
     * 格式化 data 中的值
     *
     * @param data 数据
     * @param fmt  格式化工具
     * @return 格式化之后的数据
     */
    public final Map<String, Object> fmtValue(Map<String, Object> data, Map<String, MetaApiFormat> fmt) {
        for (Map.Entry<String, Object> entry : data.entrySet()) {
            entry.setValue(fmtValue(entry.getValue(), fmt.get(entry.getKey())));
        }
        return data;
    }

    public final Object fmtValue(Object value, MetaApiFormat fmt) {
        if (value == null) return null;

        if (fmt == null) {
            if (value instanceof Date) {
                return value.toString();
            } else if (value instanceof byte[]) {
                return new String(((byte[]) value), StandardCharsets.UTF_8);
            }
        } else {
            switch (value) {
                case String s -> {
                    //转换json/array
                    return fmt.parse(s);
                    //转换json/array
                }
                case Timestamp timestamp -> {
                    return fmt.format(timestamp.toLocalDateTime());
                }
                case Number ignored -> {
                    return fmt.format(value);
                }
                case java.sql.Date date -> {
                    return fmt.format(date.toLocalDate());
                }
                case java.sql.Time time -> {
                    return fmt.format(time.toLocalTime());
                }
                case TemporalAccessor ignored -> {
                    return fmt.format(value);
                }
                case Collection<?> cs -> {
                    //请求参数中带有字符串数组，需要解析
                    List<Object> ls = new ArrayList<>(cs.size());
                    for (Object o : cs) {
                        ls.add(fmt.parse(o.toString()));
                    }
                    return ls;
                }
                case Serializable ignored -> {
                    // PGobject parse JsonObject
                    return fmt.parse(value.toString());
                    // PGobject parse JsonObject
                }
                default -> {
                }
            }
        }
        return value;
    }

    private MetaApiDefinitionBean getBean(ActionType action, String form) {
        MetaApiDefinitionBean bean = new MetaApiDefinitionBean();
        bean.setAction(action);
        bean.setFrom(form);
        return bean;
    }
}
